/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package endPoint.codeLayer;

/**
 * Implementa un oggetto capace di decodificare le stringhe codificate sendondo il codice di 
 * Hamming. Questo oggetto ha necessità di conoscere la matrice generatrice trasposta usata per codificare 
 * le stringhe che riceve, al fine di conoscere gli insiemi di bit controllati da ogni bit di controllo.
 * Si assume che i bit di controllo presenti nella parola da decodificare si trovino in posizioni 
 * corrispondenti ad indici multipli di 2 (considerando 1 come indice della prima posizione).
 * Spesso i valori binari sono espressi utilizzando variabili booleane. In questi casi il valore true è fatto
 * corrispondere al bit 1 e il valore false al bit 0.
 * 
 * @author calvelli
 */
public class HammingDecoder implements Decoder{
    
    private boolean[][] generatingMatrix;
    
    /**
     * Costruisce un HammingDecoder a partire dalla matrice generatrice ricevuta come parametro.
     * Si assume che la matrice generatrice sia trasposta.
     * 
     * @param G : matrice booleana.
     */
    public HammingDecoder(boolean[][] G){
        generatingMatrix = G;
    }//costruttore
    
    /**
     * Crea la matrice di parità a partire dalla matrice generatrice.
     *
     * @return la matrice booleana di parità
     */
    public boolean[][] createParityMatrix() {
        int controlBits = generatingMatrix.length - generatingMatrix[0].length;
        boolean[][] H = new boolean[generatingMatrix.length][controlBits];
        int nextPositionControlBit = 1;
        int nextIndexIdentityMatrix = 0;
        //int nextIndexInformationBit = 0; //si sposta sulle colonne di G (che è trasposta)
        int[] positionControlBits = new int[controlBits]; //memorizza le righe di G che corrispondono ai vari bit di controllo

        //riempio positionControlBits
        for (int findedControlBits = 0; findedControlBits < controlBits; findedControlBits++) {
            positionControlBits[findedControlBits] = nextPositionControlBit - 1;
            H[nextPositionControlBit - 1][nextIndexIdentityMatrix] = true; //inserisco la matrice identità in corrispondenza dei bit di controllo 
            nextPositionControlBit *= 2;
            nextIndexIdentityMatrix++;
        }//for findedControlBits

        nextPositionControlBit = 0; //scorre su positionControlBits per saltare le righe di H già riempite con la matrice identità   
        int currentInformationBit = 0; //dice quale bit informazione corrisponde alla riga che sto riempiendo (dice se è il primo, il secondo etc...)
        for (int i = 0; i < H.length; i++) {
            if (nextPositionControlBit < controlBits && i == positionControlBits[nextPositionControlBit]) {
                nextPositionControlBit++;
            }//if(i == nextPositionControlBit) 
            else {

                for (int j = 0; j < H[0].length; j++) {
                    H[i][j] = generatingMatrix[positionControlBits[j]][currentInformationBit];

                }//for j
                currentInformationBit++;
            }//else
        }//for i
        return H;
    }//createParityMatrix

   

    /**
     * Decodifica la stringa ricevuta come parametro.
     *
     * @param received : stringa da decodificare.
     * @return la stringa decodificata
     */
    public String decode(String received) {
        String word = checkError(received);
        System.out.println("errore: " + word);
        String result = new String();
        int currentPositionControlBit = 1, nextPositionControlBit = currentPositionControlBit * 2;
        while (nextPositionControlBit <= word.length()) {
            result = result + word.substring(currentPositionControlBit, nextPositionControlBit - 1);
            currentPositionControlBit = nextPositionControlBit;
            nextPositionControlBit *= 2;
        }//while
        result += word.substring(currentPositionControlBit);
        return result;
    }//decode

    /**
     * Corregge eventuali errori nella stringa ricevuta
     *
     * @param generatingMatrix : matrice generatrice con cui è stata costruita la stringa da
     * decodificare
     * @param received : la stringa ricevuta dal decodificatore
     * @return la stringa corretta
     */
    private String checkError(String received) {
        boolean[][] parityMatrix = createParityMatrix();
        boolean[] syndrome = createSyndrome(received, parityMatrix);
        int errorPosition = findErrorBit(parityMatrix, syndrome);
        if (errorPosition == -1) {
            return received; //vuol dire che la sindrome è composta solo da zeri e quindi non sono stati trovati errori
        }
        if (errorPosition == -2) {
            throw new RuntimeException("Errore!! Sindrome non trovata");
        }

        String correctWord = new String();
        correctWord = correctWord.concat(received.substring(0, errorPosition));

        if (received.charAt(errorPosition) == '0') {
            correctWord = correctWord.concat("1");
        } else {
            correctWord = correctWord.concat("0");
        }//else

        correctWord = correctWord.concat(received.substring(errorPosition + 1, received.length()));
        return correctWord;
    }//checkError

    /**
     * cerca la riga della matrice di parità che coincide con la sindrome.
     *
     * @param parityMatrix: matrice di parità;
     * @param syndrome
     * @return -1 se la sindrome contiene solo false, -2 se non ha trovato righe
     * uguali (errore), l'indice della riga uguale alla sindrome facendo partire
     * gli indici da 0 altrimenti;
     */
    private int findErrorBit(boolean[][] parityMatrix, boolean[] syndrome) {
        boolean existingError = false;
        for (int i = 0; i < syndrome.length; i++) {
            existingError |= syndrome[i];
        }//for

        if (!existingError) {
            return -1;
        }

        boolean finded = true;
        //scorro le righe della matrice 
        for (int i = 0; i < parityMatrix.length; i++) {
            finded = true;
            for (int j = 0; j < parityMatrix[0].length; j++) {
                if (parityMatrix[i][j] != syndrome[j]) {
                    finded = false;
                    break;
                }//if
            }//for j
            if (finded) {
                return i;
            }
        }//for i
        return -2;
    }//findErrorBit

    /**
     * calcola la sindrome calcolando il prodotto scalare tra le matrice di
     * parità e la stringa codificata ricevuta;
     *
     * @param vector: la stringa codificata ricevuta;
     * @param matrix: la matrice di parità;
     * @return un vettore con false in gni posizione se non ci sono errori
     */
    private boolean[] createSyndrome(String vector, boolean[][] matrix) {
        boolean[] syndrome = new boolean[matrix[0].length];
        for (int syndromeIndex = 0; syndromeIndex < syndrome.length; syndromeIndex++) {
            for (int i = 0; i < vector.length(); i++) {
                if (vector.charAt(i) == '1') {
                    syndrome[syndromeIndex] ^= matrix[i][syndromeIndex];
                }//if
            }//for i
        }//for syndrome
        return syndrome;
    }//createSyndrome
    
}//HammingDecoder
